<template>
  <div class="app-container">
    <div id="container"></div>
  </div>
</template>
<script>
import * as THREE from 'three'
// 鼠标控制
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
// 标签-引入CSS2渲染器CSS2DRenderer和CSS2模型对象CSS2DObject
import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'

export default {
  data() {
    return {
      scene: null, // 场景对象
      mesh: null, // 网格模型对象Mesh
      camera: null, // 相机对象
      controls: null, // 控件对象
      renderer: null, // 渲染器对象
      css2dRenderer: null, // 标签
      container: null, // 容器
      width: null, // 窗口宽度
      height: null, // 窗口高度
      k: null, // 窗口宽高比
      s: 200 // 三维场景显示范围控制系数，系数越大，显示的范围越大
    }
  },

  mounted() {
    this.init()
  },

  methods: {
    // 初始化
    init() {
      // 初始数据
      this.container = document.getElementById('container')
      this.width = window.innerWidth
      this.height = window.innerHeight
      this.k = this.width / this.height

      // 初始化场景
      this.initScene()
      // 初始化Mesh物体模型
      this.initMesh()
      // 初始化灯光
      this.initLight()
      // 初始化相机
      this.initCamera()
      // 初始化坐标轴辅助对象
      this.initAxis()
      // 初始化CSS2D标签
      this.initCSS2DRenderer()
      // 初始化渲染器
      this.initRender()
      // 初始化轨道控制器
      this.initControls()
    },

    /**
     * 创建场景
     */
    initScene() {
      this.scene = new THREE.Scene()
      this.scene.background = new THREE.Color(0x050505) // 场景背景色
    },

    /**
     * 创建物体模型
     * 立方缓冲几何体 参数：X轴上面的宽度，Y轴上面的高度，Z轴上面的深度... const geometry = new THREE.BoxGeometry(100, 100, 100);
     * 圆形缓冲几何体 参数：半径，分段... const geometry = new THREE.CircleGeometry(100, 32);
     * 圆锥缓冲几何体 参数：底部半径，高度，圆周分段数... const geometry = new THREE.ConeGeometry( 100, 400, 32 );
     * 圆柱缓冲几何体 参数：顶部半径，底部半径，高度，圆周分段数... const geometry = new THREE.CylinderGeometry( 50, 50, 100, 25 );
     * 边缘几何体 参数：每条线段的两个顶点，闲的材质.. const geometry = new THREE.EdgesGeometry( geometry );
     * 平面缓冲几何体 参数：平面沿着X轴的宽度，平面沿着Y轴的高度... const geometry = new THREE.PlaneGeometry( 100, 100 );
     * 球缓冲几何体 参数：半径，水平分段数，垂直分段数... const geometry = new THREE.SphereGeometry(60, 40, 40);
     * 圆环缓冲几何体 参数：环面的半径，管道的半径，管道横截面的分段数，管道的分段数... const geometry = new THREE.TorusGeometry( 100, 30, 16, 100 );
     * 正八面体 const geometry = new THREE.OctahedronGeometry(50);
     * 正十二面体 const geometry = new THREE.DodecahedronGeometry(50);
     * 正二十面体 参数：radius-半径，detail-大于0时会为它增加一些顶点，将不再是一个二十面体；大于1时，会变成一个球体（默认值0）  const geometry = new THREE.IcosahedronGeometry(50, 0);
     */
    initMesh() {
      // 物体1
      let geometry1 = new THREE.BoxGeometry(100, 100, 100) // 创建一个立方体几何体
      // 材质对象 Material
      let material = new THREE.MeshLambertMaterial({
        color: 0xff0000, // 设置颜色
        transparent: true, // 开启透明
        opacity: 0.5 // 设置透明度
        // wireframe: true // 线条模式渲染mesh对应的三角形数据
      })
      this.mesh = new THREE.Mesh(geometry1, material) // 物体对象Mesh
      this.scene.add(this.mesh) // 物体添加到场景中
      // 物体2
      let geometry2 = new THREE.CylinderGeometry(50, 60, 100, 25) // 圆柱体几何体
      let mesh2 = new THREE.Mesh(geometry2, material) // 物体对象Mesh
      mesh2.translateX(140) // 物体沿X轴正方向平移140
      // 或者设置mesh物体对象的xyz坐标为120,0,0
      // mesh.position.set(120,0,0)
      this.scene.add(mesh2) // 物体添加到场景中
      // 自定义几何体
      const geometry3 = new THREE.BufferGeometry()
      //创建顶点数据
      const vertices = new Float32Array([
        // 顶点1坐标
        0, 0, 0,
        // 顶点2坐标
        100, 0, 0,
        // 顶点3坐标
        0, 100, 0,
        // 顶点4坐标
        0, 0, 30,
        // 顶点5坐标
        0, 0, 100,
        // 顶点6坐标
        60, 0, 20
      ])
      // 创建属性缓冲区对象
      const attribute = new THREE.BufferAttribute(vertices, 3)
      // 设置几何体attributes属性的位置属性
      geometry3.attributes.position = attribute
      const material3 = new THREE.MeshBasicMaterial({
        color: 0x00ff00,
        // side: THREE.FrontSide, //默认只有正面可见
        side: THREE.DoubleSide //两面可见
        // side: THREE.BackSide, //设置只有背面可见
      })
      const mesh3 = new THREE.Mesh(geometry3, material3)
      mesh3.translateX(-200) // 物体沿X轴负方向平移200
      this.scene.add(mesh3)
    },

    /**
     * 光源设置
     */
    initLight() {
      // 1.环境光
      const ambient = new THREE.AmbientLight(0x444444)
      this.scene.add(ambient) // 环境光添加到场景中

      // 2.点光源
      const point = new THREE.PointLight(0xffffff)
      point.position.set(400, 200, 300) // 点光源位置
      this.scene.add(point) // 点光源添加到场景中

      // 创建点光源辅助对象
      const pointLightHelper = new THREE.PointLightHelper(point, 10)
      this.scene.add(pointLightHelper)

      // 3.平行光
      const directionalLight = new THREE.DirectionalLight(0xffffff, 1)
      // 设置光源的方向：通过光源position属性和目标指向对象的position属性计算光线的方向
      directionalLight.position.set(80, 100, 50)
      // 光的方向指向对象网格模型mesh，不设置默认为0,0,0
      directionalLight.target = this.mesh
      this.scene.add(directionalLight)

      // 创建平行光辅助对象
      const directionalLightHelper = new THREE.DirectionalLightHelper(directionalLight, 10, 0x00ff00)
      this.scene.add(directionalLightHelper)

      // 4.聚光灯
      const spotLight = new THREE.SpotLight(0xffffff)
      spotLight.position.set(0, 200, 100)
      this.scene.add(spotLight)

      // 创建聚光灯辅助对象
      const spotLightHelper = new THREE.SpotLightHelper(spotLight, 0x0000ff)
      this.scene.add(spotLightHelper)
    },

    /**
     * 相机设置
     */
    initCamera() {
      // 创建相机对象
      this.camera = new THREE.OrthographicCamera(-this.s * this.k, this.s * this.k, this.s, -this.s, 1, 1000)
      this.camera.position.set(200, 400, 600) // 设置相机位置
      this.camera.lookAt(this.scene.position) // 设置相机方向(指向的场景对象)
    },

    /**
     * 坐标轴辅助对象
     */
    initAxis() {
      let axisHelper = new THREE.AxesHelper(250)
      this.scene.add(axisHelper)
    },

    /**
     * 设置模型标签
     */
    initCSS2DRenderer() {
      // 添加CSS 2DObject标签
      // 创建div元素(作为标签)
      var div = document.createElement('div')
      div.innerHTML = 'CSS 2DObject 标签'
      div.classList.add('tag')
      div.style.pointerEvents = 'none' // 避免HTML标签遮挡三维场景的鼠标事件
      // div元素包装为CSS2模型对象CSS2DObject
      var label2D = new CSS2DObject(div)
      // 设置HTML元素标签在three.js世界坐标中位置
      // label2D.position.set(0, 200, 100)
      var pos1 = new THREE.Vector3()
      this.mesh.getWorldPosition(pos1) // 获取obj世界坐标、
      pos1.y += 30
      label2D.position.copy(pos1) // 标签标注在obj世界坐标
      this.scene.add(label2D) // 标签插入场景
      // 添加到组中
      const group = new THREE.Group()
      group.add(label2D)
      this.mesh.add(label2D) // 添加到Mesh中
    },

    /**
     * 创建渲染器对象
     */
    initRender() {
      this.renderer = new THREE.WebGLRenderer()
      this.renderer.setSize(this.width, this.height) // 设置渲染区域尺寸
      this.renderer.setClearColor(0xb9d3ff, 1) // 设置背景颜色
      this.container.appendChild(this.renderer.domElement) // body元素中插入canvas对象

      var labelRenderer = new CSS2DRenderer()
      labelRenderer.setSize(this.width, this.height)
      labelRenderer.domElement.style.position = 'absolute'
      // 相对标签原位置位置偏移大小
      labelRenderer.domElement.style.top = '0px'
      labelRenderer.domElement.style.left = '0px'
      // 设置.pointerEvents=none，以免模型标签HTML元素遮挡鼠标选择场景模型
      labelRenderer.domElement.style.pointerEvents = 'none'
      this.container.appendChild(labelRenderer.domElement)

      let render = () => {
        this.mesh.rotateY(0.01) // 沿y轴旋转的弧度，单位 弧度
        // 执行渲染操作   指定场景、相机作为参数
        this.renderer.render(this.scene, this.camera)
        // 渲染HTML标签对象 CSS2DObject 标签
        labelRenderer.render(this.scene, this.camera)
        // requestAnimationFrame 实现周期性循环执行
        // 请求再次执行渲染函数render，渲染下一帧
        requestAnimationFrame(render)
      }
      render()
    },

    /**
     * 轨道控制器
     * 成功！可以任意拖动
     * 缩放：滚动—鼠标中键
     * 旋转：拖动—鼠标左键
     * 平移：拖动—鼠标右键
     */
    initControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement) // 创建控件对象

      // // 监听轨道控制器的change事件，监听到改变时，重新执行渲染操作渲染三维场景
      // this.controls.addEventListener('change', function () {
      //   this.renderer.render(this.scene, this.camera)
      // })
    }
  }
}
</script>

<style scoped>
#container {
  height: 400px;
}
</style>
